﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using OpenLS.Spreadsheet.Expressions;
using OpenLS.Spreadsheet.Formats.Biff.Tokens;

namespace OpenLS.Spreadsheet.Formats.Biff.Records
{
    abstract class ParsedFormulaBase
    {
        private Stack<FormulaToken> _tokens = new Stack<FormulaToken>();
        public int TokenCount{get{ return _tokens.Count;}}
        protected void WriteCell(FormulaWriteContext formulaWriteContext)
        {
            ushort u = TotalTokenByteLength(formulaWriteContext.Type);
            formulaWriteContext.Writer.Write(u);
            WriteNamed(formulaWriteContext);
        }

        public void WriteNamed(FormulaWriteContext formulaWriteContext)
        {
            List<ComplexFormulaToken> complexFormulaTokens = new List<ComplexFormulaToken>();
            foreach (var token in Enumerable.Reverse(_tokens))
            {
                token.Write(formulaWriteContext);
                if (token is ComplexFormulaToken)
                    complexFormulaTokens.Add((ComplexFormulaToken)token);
            }

            foreach (var complexFormulaToken in complexFormulaTokens)
            {
                complexFormulaToken.WriteAfter(formulaWriteContext.Writer);
            }
        }
        protected void WriteNoLength(FormulaWriteContext formulaWriteContext)
        {
            List<ComplexFormulaToken> complexFormulaTokens = new List<ComplexFormulaToken>();
            foreach (var token in Enumerable.Reverse(_tokens))
            {
                token.Write(formulaWriteContext);
                if (token is ComplexFormulaToken)
                    complexFormulaTokens.Add((ComplexFormulaToken)token);
            }

            foreach (var complexFormulaToken in complexFormulaTokens)
            {
                complexFormulaToken.WriteAfter(formulaWriteContext.Writer);
            }
        }
       
        internal static T GetFormulaValue<T>
            (BiffWriterWorkbookContext context, Expression expression, Cell cell) where T: ParsedFormulaBase, new()
        {
            Contract.Requires(context != null);
            Contract.Requires(cell != null || typeof (T) != typeof (CFParsedFormulaNoCCE));
            T result = new T();
            if (expression != null)
            {
                FormulaTokenBuilder formulaTokenBuilder = new FormulaTokenBuilder(context, AccessorType.Value, result,
                                                                                  cell, result.GetReferenceCell(cell));
                formulaTokenBuilder.Apply(expression);
                result._tokens = formulaTokenBuilder.Tokens;
            }
            else
            {
                result._tokens = new Stack<FormulaToken>();
            }
            //  result.Expression = expression;
            return result;
        }

        protected abstract Cell GetReferenceCell(Cell cell);


        public Expression GetExpression(BiffReaderWorksheetContext context, Cell target)
        {
            Contract.Assert(_tokens.Count > 0);
            FormulaToken t = _tokens.Pop();
            Expression exp = t.GetExpression(new FormulaEvaluationContext(context, _tokens, target));
            return exp;
        }
#if DEBUG

        internal IEnumerable<FormulaToken> GetTokens()
        {
            List<FormulaToken> temp = new List<FormulaToken>(_tokens);
            temp.Reverse();
            return temp;
        }
#endif





        protected void Read(FormulaReadContext context, ushort formulaLength)
        {
            _tokens = new Stack<FormulaToken>();
            var fr = new FormulaTokenReader(context);
            int i = 0;
            var tokens = new List<ComplexFormulaToken>();
            while (i < formulaLength)
            {
                var posBefore = (int) context.Reader.BaseStream.Position;
                int oldI = i;
                FormulaToken t = fr.ReadToken();
                i += t.GetLength(context.Type) + 1;
                var posAfter = (int)context.Reader.BaseStream.Position;
                Contract.Assert(posAfter - posBefore == i - oldI);
                _tokens.Push(t);
                if (t is ComplexFormulaToken)
                    tokens.Add((ComplexFormulaToken) t);
            }
            foreach (var t in tokens)
            {
                t.ReadAfter(context.Reader);   
            }
        }

        public Expression GetExpression(BiffReaderWorkbookContext context)
        {
            if (_tokens.Count == 0)
                return null;
            Workbook workbook = context.Workbook;
            Contract.Assert(_tokens.Count > 0);
            FormulaToken t = _tokens.Pop();
            Expression exp = t.GetExpression(new FormulaEvaluationContext(context, _tokens, workbook));
            return exp;
        }

        public ushort TotalTokenByteLength (FormulaReadContextType type)
        {
            
            {
                Contract.Assert(_tokens != null);
                return (ushort) (Enumerable.Sum(_tokens, t => t.GetLength(type)) + _tokens.Count);
            }
        }

        public override string ToString()
        {
            return GetType().Name + " " +  "{" + string.Join(",", Enumerable.ToList( Enumerable.Select(_tokens, t => t.ToString())).ToArray()) + "}";
        }

        public Expression GetExpression(BiffReaderWorksheetContext context, CFFormulaContext target)
        {
            Contract.Assert(_tokens.Count > 0);
            FormulaToken t = _tokens.Pop();
            Expression exp = t.GetExpression(new FormulaEvaluationContext(context, _tokens, target));
            return exp;
        }
    }
}